﻿#region Copyright (c) 2013-06, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
using Amarok.Agents;


namespace Amarok.Presentation
{
	/// <summary>
	/// This type represents a base class for controller implementations. Basically, a controller is an agent 
	/// with additional presentation-specific functionality.
	/// </summary>
	/// 
	/// <typeparam name="TViewModel">
	/// The type of the view model that is used as intermediate between controller and view.</typeparam>
	public abstract class Controller<TViewModel> : Controller
		where TViewModel : new()
	{
		#region ## Protected Interface ##

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// 
		/// <param name="environment">
		/// The agent environment for this agent.</param>
		/// <param name="options">
		/// The agent options for this agent.</param>
		/// <param name="view">
		/// The view associated with this controller.</param>
		/// <param name="viewModel">
		/// The view model that is used as intermediate between controller and view.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		protected Controller(AgentEnvironment environment, AgentOptions options, IView view, TViewModel viewModel)
			: base(environment, options, view, viewModel)
		{
		}

		#endregion

		#region ## Protected Interface (AccessViewModel) ##

		/// <summary>
		/// Gives the supplied action access to the view model. The supplied action is synchronously executed in 
		/// the thread that created the associated view. The method blocks until the action returns.
		/// </summary>
		/// 
		/// <param name="action">
		/// The action that gains access to the view model.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		protected void AccessViewModel(Action<TViewModel> action)
		{
			Verify.NotNull(action, "action");

			this.View.Invoke(() =>
				{
					// call callback
					action(this.ViewModel);
				});
		}

		#endregion

		#region ## Protected Interface (AccessViewModelAsync) ##

		/// <summary>
		/// Gives the supplied action access to the view model. The supplied action is asynchronously executed in 
		/// the thread that created the associated view. The method returns immediately.
		/// </summary>
		/// 
		/// <param name="action">
		/// The action that gains access to the view model.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running operation. This task can be used to wait for operation 
		/// completion, to register a continuation and to access state and result of the operation.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		protected Task AccessViewModelAsync(Action<TViewModel> action)
		{
			Verify.NotNull(action, "action");

			var task = this.View.InvokeAsync(() =>
				{
					try
					{
						// call callback
						action(this.ViewModel);
					}
					catch (Exception exception)
					{
						#region (tracing)
						{
							Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
								"*** ERROR: action '{0}' supplied to AccessViewModelAsync() threw an exception:\r\n\r\n{1}",
								action,
								Helper.FormatException(exception, CultureInfo.InvariantCulture)));
						}
						#endregion

						throw;
					}
				});

			return task.IgnoreExceptions();
		}

		#endregion

		#region ## Protected Interface (RunViewModelAsync) ##

		/// <summary>
		/// Runs the supplied action asynchronously on a background thread.
		/// 
		/// Before running the actual action a prepare action is run. This action is called in the context of the
		/// user interface thread and provides direct access to the view model.
		/// 
		/// After completion or failure either the success action or the failure action is called. Both actions 
		/// will be called in the context of the user interface thread and provide direct access to the view model.
		/// </summary>
		/// 
		/// <param name="prepareAction">
		/// The action is run before the actual action. This action is called in the context of the user interface 
		/// thread and provides immediate access to the view model.</param>
		/// <param name="asyncAction">
		/// The action to run asynchronously on a background thread.</param>
		/// <param name="successAction">
		/// The action that is run in case of successful completion. This action is called in the context of the
		/// user interface thread and provides immediate access to the view model.</param>
		/// <param name="failureAction">
		/// The action that is run in case of a failure. This action is called in the context of the user interface 
		/// thread and provides immediate access to the view model and the exception that caused the failure.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		protected Task<TResult> RunViewModelAsync<TResult>(
			Action<TViewModel> prepareAction,
			Func<TResult> asyncAction,
			Action<TViewModel, TResult> successAction,
			Action<TViewModel, Exception> failureAction)
		{
			Verify.NotNull(prepareAction, "prepareAction");
			Verify.NotNull(asyncAction, "action");
			Verify.NotNull(successAction, "successAction");
			Verify.NotNull(failureAction, "failureAction");

			try
			{
				// preparation: run in user interface thread
				this.AccessViewModel(viewModel =>
					{
						prepareAction(viewModel);
					});
			}
			catch (Exception prepareActionException)
			{
				// return an already-completed-but-failed task
				var taskSource = new TaskCompletionSource<TResult>();
				taskSource.SetException(prepareActionException);
				return taskSource.Task.IgnoreExceptions();
			}

			var task = Task.Factory.StartNew<TResult>(() =>
			{
				try
				{
					// run asynchronously
					var result = asyncAction();

					try
					{
						// success: run in user interface thread
						this.AccessViewModel(viewModel =>
							{
								successAction(viewModel, result);
							});
					}
					catch (Exception successActionException)
					{
						#region (tracing)
						{
							Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
								"*** ERROR: Success action '{0}' supplied to RunViewModelAsync() threw an exception:" +
								"\r\n\r\n{1}",
								successAction,
								Helper.FormatException(successActionException, CultureInfo.InvariantCulture)));
						}
						#endregion

						// ignore
					}

					// forward result value to returned task
					return result;
				}
				catch (Exception actionException)
				{
					try
					{
						// failure: run in user interface thread
						this.AccessViewModel(viewModel =>
							{
								failureAction(viewModel, actionException);
							});
					}
					catch (Exception failureActionException)
					{
						#region (tracing)
						{
							Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
								"*** ERROR: Failure action '{0}' supplied to RunViewModelAsync() threw an exception:" +
								"\r\n\r\n{1}",
								successAction,
								Helper.FormatException(failureActionException, CultureInfo.InvariantCulture)));
						}
						#endregion

						// ignore
					}

					// forward exception to returned task
					throw;
				}
			});

			return task.IgnoreExceptions();
		}

		#endregion

		#region ## Overridable Methods ##

		/// <summary>
		/// This method is called once during construction to give derived types a chance to pre-initialize the 
		/// view model before it is assigned to the view. Override this method to initialize the given view model
		/// to its initial state, e.g. instantiate and assign commands.
		/// 
		/// Note that there is no need to raise property changed notifications, because the view model is not assigned 
		/// to the view yet.
		/// 
		/// Warning: This method is called during construction of the controller, meaning it is called from the 
		/// base class's constructor, thus, it is possible that this method is called before your own constructor. 
		/// Therefore, it is recommended to use this method just to initialize the given view model instance. Don't
		/// try to post or publish messages, don't access local member fields, and so forth.
		/// </summary>
		/// 
		/// <param name="viewModel">
		/// A reference to the view model instance that should be initialized.</param>
		protected virtual void OnPresetViewModel(TViewModel viewModel)
		{
			// default implementation
		}

		#endregion

		#region ~~ Internal Interface ~~

		/// <summary>
		/// </summary>
		internal new TViewModel ViewModel
		{
			get
			{
				return (TViewModel)base.ViewModel;
			}
		}

		#endregion

		#region Implementation

		/// <summary>
		/// This method is called once during construction to give derived types a chance to pre-initialize the 
		/// view model before it is assigned to the view. Override this method to initialize the given view model
		/// to its initial state, e.g. instantiate and assign commands.
		/// 
		/// Note that there is no need to raise property changed notifications, because the view model is not 
		/// assigned to the view yet.
		/// 
		/// Warning: This method is called during construction of the controller, meaning it is called from the 
		/// base class's constructor, thus, it is possible that this method is called before your own constructor. 
		/// Therefore, it is recommended to use this method just to initialize the given view model instance. Don't
		/// try to post or publish messages, don't access local member fields, and so forth.
		/// 
		/// This method is called in the UI dispatcher thread.
		/// </summary>
		/// 
		/// <param name="viewModel">
		/// A reference to the view model instance that should be initialized.</param>
		protected sealed override void OnPresetViewModel(Object viewModel)
		{
			// forward to type-safe overload
			OnPresetViewModel((TViewModel)viewModel);
		}

		#endregion

	}
}
